/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package edi.dao;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Persistable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;

@Repository
@Transactional
public abstract class HibernateCrudDao<T extends Persistable<Long>> implements CrudDao<T> {

	private Class<T> clazz;

	@Autowired
	private SessionFactory sessionFactory;

	public HibernateCrudDao(Class<T> clazz) {
		this.clazz = clazz;
	}

	protected Session session() {
		return sessionFactory.getCurrentSession();
	}

	@Override
	@Transactional(readOnly = true)
	public long count() {
		return ((Long) session().createQuery("select count(*) from " + clazz.getName()).uniqueResult())
				.longValue();
	}

	@Override
	public void delete(Long id) {
		T entity = findOne(id);
		delete(entity);
	}

	@Override
	public void delete(T entity) {
		session().delete(entity);
	}

	@Override
	public void delete(Iterable<? extends T> iterable) {
		if (iterable != null) {
			for (T entity : iterable) {
				session().delete(entity);
			}
		}
	}

	@Override
	public void deleteAll() {
		session().createQuery("delete from " + clazz.getName()).executeUpdate();
	}

	@Override
	@Transactional(readOnly = true)
	public boolean exists(Long id) {
		if (id == null) {
			throw new HibernateException("id is null");
		} else {
			return findOne(id) != null;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional(readOnly = true)
	public List<T> findAll() {
		return session().createQuery("from " + clazz.getName()).list();
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional(readOnly = true)
	public List<T> findAll(Iterable<Long> ids) {
		if (ids == null || !ids.iterator().hasNext()) {
			return new ArrayList<>();
		} else {
			Query query = session().createQuery(
					"select e from " + clazz.getName() + " e WHERE e.id in (:ids)").setParameterList("ids",
					Lists.newArrayList(ids));
			return query.list();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional(readOnly = true)
	public T findOne(Long id) {
		return (T) session().get(clazz, id);
	}

	@Override
	@Transactional(readOnly = false)
	public <S extends T> S save(S entity) {
		session().save(entity);
		return entity;
	}

	protected <S extends T> S update(S entity) {
		session().merge(entity);
		session().flush();
		return entity;
	}

	@Override
	@Transactional(readOnly = false)
	public <S extends T> S saveOrUpdate(S entity) {
		if (entity.isNew()) {
			save(entity);
		} else {
			update(entity);
		}

		return entity;
	}

	@Override
	@Transactional(readOnly = false)
	public <S extends T> List<S> saveOrUpdate(Iterable<S> entities) {
		List<S> result = new ArrayList<S>();
		if (entities == null) {
			return result;
		}
		for (S entity : entities) {
			result.add(saveOrUpdate(entity));
		}
		return result;
	}

	@Override
	@Transactional(readOnly = false)
	public <S extends T> List<S> save(Iterable<S> entities) {
		List<S> result = new ArrayList<S>();
		if (entities == null) {
			return result;
		}
		for (S entity : entities) {
			result.add(save(entity));
		}
		return result;
	}
}